/*
 * Copyright 2022 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef skgpu_graphite_DrawParams_DEFINED
#define skgpu_graphite_DrawParams_DEFINED


#include "include/core/SkPaint.h"
#include "include/core/SkRect.h"
#include "src/gpu/graphite/DrawOrder.h"
#include "src/gpu/graphite/geom/Geometry.h"
#include "src/gpu/graphite/geom/Rect.h"
#include "src/gpu/graphite/geom/Transform_graphite.h"

#include <optional>

namespace skgpu::graphite {
// NOTE: Only represents the stroke or hairline styles; stroke-and-fill must be handled higher up.
class StrokeStyle {
public:
    StrokeStyle() : fHalfWidth(0.f), fJoinLimit(0.f), fCap(SkPaint::kButt_Cap) {}
    StrokeStyle(float width, float miterLimit, SkPaint::Join join, SkPaint::Cap cap)
        : fHalfWidth(std::max(0.f, 0.5f * width)),
          fJoinLimit(join == SkPaint::kMiter_Join ? std::max(0.f, miterLimit) :
                                                    (join == SkPaint::kBevel_Join ? 0.f : -1.f)),
          fCap(cap)
    {}

    StrokeStyle(const StrokeStyle &) = default;

    StrokeStyle &operator = (const StrokeStyle &) = default;

    bool isMiterJoin() const
    {
        return fJoinLimit > 0.f;
    }
    bool isBevelJoin() const
    {
        return fJoinLimit == 0.f;
    }
    bool isRoundJoin() const
    {
        return fJoinLimit < 0.f;
    }

    float halfWidth() const
    {
        return fHalfWidth;
    }
    float width() const
    {
        return 2.f * fHalfWidth;
    }
    float miterLimit() const
    {
        return std::max(0.f, fJoinLimit);
    }
    SkPaint::Cap cap() const
    {
        return fCap;
    }
    SkPaint::Join join() const
    {
        return fJoinLimit > 0.f ? SkPaint::kMiter_Join :
                                  (fJoinLimit == 0.f ? SkPaint::kBevel_Join : SkPaint::kRound_Join);
    }

    // Raw join limit, compatible with tess::StrokeParams
    float joinLimit() const
    {
        return fJoinLimit;
    }

private:
    float fHalfWidth; // >0: relative to transform; ==0: hairline, 1px in device space
    float fJoinLimit; // >0: miter join; ==0: bevel join; <0: round join
    SkPaint::Cap fCap;
};

// TBD: Separate DashParams extracted from an SkDashPathEffect? Or folded into StrokeStyle?

class Clip {
public:
    Clip() = default;
    Clip(const Rect &drawBounds, const Rect &shapeBounds, const SkIRect &scissor, const SkShader *shader)
        : fDrawBounds(drawBounds), fTransformedShapeBounds(shapeBounds), fScissor(scissor), fShader(shader)
    {}

    // Tight bounds of the draw, including any padding/outset for stroking and expansion due to
    // inverse fill and intersected with the scissor.
    const Rect &drawBounds() const
    {
        return fDrawBounds;
    }

    // The scissor rectangle obtained by restricting the bounds of the clip stack that affects the
    // draw to the device bounds. The scissor must contain drawBounds() and must already be
    // intersected with the device bounds.
    const SkIRect &scissor() const
    {
        return fScissor;
    }

    // Clipped bounds of the shape in device space, including any padding/outset for stroking,
    // intersected with the scissor and ignoring the fill rule. For a regular fill this is identical
    // to drawBounds(). For an inverse fill, this is a subset of drawBounds().
    const Rect &transformedShapeBounds() const
    {
        return fTransformedShapeBounds;
    }

    // If set, the clip shader's output alpha is further used to clip the draw.
    const SkShader *shader() const
    {
        return fShader;
    }

    bool isClippedOut() const
    {
        return fDrawBounds.isEmptyNegativeOrNaN();
    }

private:
    // DrawList assumes the DrawBounds are correct for a given shape, transform, and style. They
    // are provided to the DrawList to avoid re-calculating the same bounds.
    Rect fDrawBounds;
    Rect fTransformedShapeBounds;
    SkIRect fScissor;
    const SkShader *fShader;

    // TODO: If we add more complex analytic shapes for clipping, e.g. coverage rrect, it should
    // go here.
};

// Encapsulates all geometric state for a single high-level draw call. RenderSteps are responsible
// for transforming this state into actual rendering; shading from PaintParams is handled separately
class DrawParams {
public:
    DrawParams(const Transform &transform, const Geometry &geometry, const Clip &clip, DrawOrder drawOrder,
        const StrokeStyle *stroke)
        : fTransform(transform),
          fGeometry(geometry),
          fClip(clip),
          fOrder(drawOrder),
          fStroke(stroke ? std::optional<StrokeStyle>(*stroke) : std::nullopt)
    {}

    const Transform &transform() const
    {
        return fTransform;
    }
    const Geometry &geometry() const
    {
        return fGeometry;
    }
    const Clip &clip() const
    {
        return fClip;
    }
    DrawOrder order() const
    {
        return fOrder;
    }

    // Optional stroke parameters if the geometry is stroked instead of filled
    bool isStroke() const
    {
        return fStroke.has_value();
    }
    const StrokeStyle &strokeStyle() const
    {
        SkASSERT(this->isStroke());
        return *fStroke;
    }

private:
    const Transform &fTransform; // Lifetime of the transform must be held longer than the geometry

    Geometry fGeometry;
    Clip fClip;
    DrawOrder fOrder;

    std::optional<StrokeStyle> fStroke; // Not present implies fill
};
} // namespace skgpu::graphite

#endif // skgpu_graphite_DrawParams_DEFINED
